/* NSC -- new Scala compiler
 * Copyright 2007-2008 LAMP/EPFL
 * @author  Sean McDirmid
 */
// $Id: ModelExtractor.scala 13089 2007-10-17 13:53:37Z michelou $

package org.scala_tools.vscaladoc

import scala.tools.nsc._
import scala.collection.jcl
import compat.Platform.{EOL => LINE_SEPARATOR}


/** This class attempts to reverse engineer source code intent from compiler
 *  symbol objects.
 *
 * @author Sean McDirmid
 */
trait ModelExtractor {
  val global: Global
  import global._
  def settings: doc.Settings

  def assert(b: Boolean) {
    if (!b)
      throw new Error;
  }

  case class Tag(tag: String, option: String, body: String)

  case class Comment(body: String, attributes: List[Tag]) {
    def decodeAttributes = {
      val map = new jcl.LinkedHashMap[String,List[(String,String)]] {
        override def default(key : String) = Nil;
      }
      attributes.foreach(a => {
        map(a.tag) = map(a.tag) ::: ((a.option,a.body) :: Nil);
      });
      map
    }
  }

  protected def decode(sym: Symbol) =
    if (sym == definitions.ScalaObjectClass || sym == definitions.ObjectClass)
      definitions.AnyRefClass
    else sym match {
      case sym : ModuleClassSymbol => sym.sourceModule
      case sym => sym
    }

  protected def decodeComment(comment0 : String) : Comment = {
    //Console.println("COMMENT: " + comment0)
  //  assert(comment0.startsWith("/**"))
//    assert(comment0.endsWith("*/"))
    val comment = comment0 // .substring("/**".length, comment0.length - "*/".length)
    val tok = new java.util.StringTokenizer(comment, LINE_SEPARATOR)
    val buf = new StringBuilder
    type AttrDescr = (String, String, StringBuilder)
    val attributes = new collection.mutable.ListBuffer[AttrDescr]
    var attr: AttrDescr = null
    while (tok.hasMoreTokens) {
      val s = tok.nextToken.replaceFirst("\\p{Space}?\\*", "")
      val mat1 = pat1.matcher(s)
      if (mat1.matches) {
        attr = (mat1.group(1), null, new StringBuilder(mat1.group(2)))
        //if (kind != CONSTRUCTOR)
        attributes += attr
      } else {
        val mat2 = pat2.matcher(s)
        if (mat2.matches) {
          attr = (mat2.group(1), mat2.group(2), new StringBuilder(mat2.group(3)))
          //if (kind != CLASS)
          attributes += attr
        } else if (attr ne null)
          attr._3.append(s + LINE_SEPARATOR)
        else
          buf.append(s + LINE_SEPARATOR)
      }
    }
    (Comment(buf.toString,attributes.toList.map({x => Tag(x._1,x._2,x._3.toString)})));
  }


  sealed abstract class Entity(val sym: Symbol) {
    private[ModelExtractor] def sym0 = sym

    override def toString = sym.toString
    def comment : Option[String] = global.comments.get(sym)
    // comments decoded, now what?
    def attributes = sym.attributes
    def decodeComment : Option[Comment] = {
      val comment0 = this.comment;
      if (comment0.isEmpty) return None;
      var comment = comment0.get.trim;
      Some(ModelExtractor.this.decodeComment(comment));
    }

    protected def accessQualified(core: String, qual: String) = core match {
      case "public" => "" // assert(qual == null); "";
      case core => core + (if (qual == null) "" else "[" + qual + "]")
    }

    def flagsString = {
      import symtab.Flags
        val isLocal = sym.hasFlag(Flags.LOCAL)
        val x =
          if (sym.hasFlag(Flags.PRIVATE)) "private"
          else if (sym.hasFlag(Flags.PROTECTED)) "protected"
          else "public"
        var string = accessQualified(x, {
          if (sym.hasFlag(Flags.LOCAL)) "this";
          else if (sym.privateWithin != null && sym.privateWithin != NoSymbol)
            sym.privateWithin.nameString;
          else null;
        });
        def f(flag: Int, str: String) =
          if (sym.hasFlag(flag)) string = string + " " + str;

        f(Flags.IMPLICIT, "implicit");
        f(Flags.SEALED, "sealed");
        f(Flags.OVERRIDE, "override");
        f(Flags.CASE, "case");
        if (!sym.isTrait) f(Flags.ABSTRACT, "abstract");
        if (!sym.isModule) f(Flags.FINAL, "final");
        if (!sym.isTrait) f(Flags.DEFERRED, "abstract");
        string.trim;
    }
    def listName = name
    def name = sym.nameString
    def fullName(sep: Char) = sym.fullNameString(sep)
    def kind: String
    def header { }
    def typeParams: List[TypeParam] = Nil
    def params: List[List[Param]] = Nil
    def resultType: Option[Type] = None
    def parents: Iterable[Type] = Nil
    def lo: Option[Type] = sym.info match {
      case TypeBounds(lo,hi) if decode(lo.typeSymbol) != definitions.AllClass => Some(lo)
      case _ => None
    }
    def hi : Option[Type] = sym.info match {
      case TypeBounds(lo,hi) if decode(hi.typeSymbol) != definitions.AnyClass => Some(hi)
      case _ => None
    }
    def variance = {
      import symtab.Flags._
      if (sym hasFlag COVARIANT) "+"
      else if (sym hasFlag CONTRAVARIANT) "-"
      else ""
    }
    def overridden: Iterable[Symbol] = Nil
  }

  class Param(sym: Symbol) extends Entity(sym) {
    override def resultType = Some(sym.tpe)
    //def kind = if (sym.isPublic) "val" else "";
    def kind = ""
  }

  class ConstructorParam(sym: Symbol) extends Param(sym) {
    override protected def accessQualified(core: String, qual: String) = core match {
      case "public" => "val"
      case "protected" => super.accessQualified(core,qual) + " val"
      case "private" if qual == "this" => ""
      case core => super.accessQualified(core, qual)
    }
  }

  def Param(sym: Symbol) = new Param(sym)
  class TypeParam(sym: Symbol) extends Entity(sym) {
    def kind = ""
  }
  def TypeParam(sym: Symbol) = new TypeParam(sym)

  trait Clazz extends ClassOrObject {
    private def csym = sym.asInstanceOf[TypeSymbol]
    override def typeParams = csym.typeParams.map(TypeParam)
    override def params = {
      if (constructorArgs.isEmpty) Nil
      else constructorArgs.values.toList :: Nil
    }
    def isTrait = csym.isTrait
    override def kind = if (sym.isTrait) "trait" else "class"
  }

  trait Object extends ClassOrObject {
    override def kind = "object"
  }

  case class Package(override val sym: ModuleSymbol) extends Entity(sym) {
    override def kind = "package"
    override def name = fullName('.')
  }

  trait TopLevel extends ClassOrObject
  class TopLevelClass (sym: Symbol) extends Entity(sym) with TopLevel with Clazz
  class TopLevelObject(sym: Symbol) extends Entity(sym) with TopLevel with Object

  def compare(pathA: List[ClassOrObject], pathB: List[ClassOrObject]): Int = {
    var pA = pathA
    var pB = pathB
    while (true) {
      if (pA.isEmpty) return -1;
      if (pB.isEmpty) return +1;
      val diff = pA.head.name compare pB.head.name;
      if (diff != 0) return diff;
      pA = pA.tail;
      pB = pB.tail;
    }
    0
  }

  def isAccessible(sym: Symbol): Boolean = {
    import symtab.Flags._
//    sym.isPublic || (sym hasFlag PROTECTED)
    settings.memberaccess.value match {
      case "private"   => sym.isPublic || (sym hasFlag PROTECTED) || (sym hasFlag PRIVATE)
      case "protected" => sym.isPublic || (sym hasFlag PROTECTED)
      case "public"    => sym.isPublic
      case _           => false
    }
  }

  trait ClassOrObject extends Entity {
    def path : List[ClassOrObject] = this :: Nil;
    override def listName = path.map(_.name).mkString("",".","");

    object freshParents extends jcl.LinkedHashSet[Type] {
      this addAll sym.tpe.parents;
      this.toList.foreach(e => this removeAll e.parents);
    }
    object constructorArgs extends jcl.LinkedHashMap[Symbol,Param] {
      sym.constrParamAccessors.foreach(arg => {
        val str = symtab.Flags.flagsToString(arg.flags);
        assert(arg.hasFlag(symtab.Flags.PRIVATE) && arg.hasFlag(symtab.Flags.LOCAL));
        val argName = arg.name.toString.trim
        val actual = sym.tpe.decls.elements.find(e => {
          val eName = e.name.toString.trim;
          argName == eName && {
            val str = symtab.Flags.flagsToString(e.flags);
            !e.hasFlag(symtab.Flags.LOCAL);
          }
        });
        if (!actual.isEmpty) this(actual.get) = new ConstructorParam(actual.get);
        else this(arg) = new ConstructorParam(arg);
      });
    }
    object decls extends jcl.LinkedHashMap[Symbol, Member] {
      sym.tpe.decls.elements.foreach(e => {
        if (!constructorArgs.contains(e)) {
          val m = Member(e)
          if (!m.isEmpty && !this.contains(e)) this.put(e, m.get)
        }
      });
    }
    def members0(f: Symbol => Boolean) = decls.projection.filterKeys(f).valueSet
    def members(c: Category): Iterable[Member] = members0(c.f)
    object inherited extends jcl.LinkedHashMap[Symbol, List[Member]]() {
      override def default(tpe: Symbol) = Nil
      for (m <- sym.tpe.members if !sym.tpe.decls.elements.contains(m) &&
          (Values.f(m) || Methods.f(m))) {
        val o = m.overridingSymbol(sym)
        if (o == NoSymbol) {
          val parent = decode(m.enclClass)
          val mo = Member(m)
          if (!mo.isEmpty) {
            this(parent) = mo.get :: this(parent);
          }
        }
      }
    }
    override def parents = freshParents
    abstract class Member(sym: Symbol) extends Entity(sym) {
      private def overriding = sym.allOverriddenSymbols
      override def comment = super.comment match {
      case ret @ Some(comment) =>
        ret
      case None =>
        val o = overriding.find(comments.contains)
        o.map(comments.apply)
      }
    }
    abstract class ValDef(sym: Symbol) extends Member(sym) {
      override def resultType = Some(resultType0)
      protected def resultType0: Type
      override def overridden: Iterable[Symbol] = {
        var ret: jcl.LinkedHashSet[Symbol] = null
        for (parent <- ClassOrObject.this.parents) {
          val sym0 = sym.overriddenSymbol(parent.typeSymbol)
          if (sym0 != NoSymbol) {
            if (ret == null) ret = new jcl.LinkedHashSet[Symbol];
            ret += sym0;
          }
        }
        if (ret == null) Nil else ret.readOnly;
      }
    }
    case class Def(override val sym : TermSymbol) extends ValDef(sym) {
      override def resultType0 = sym.tpe.finalResultType;
      override def typeParams = sym.tpe.typeParams.map(TypeParam);
      override def params = methodArgumentNames.get(sym) match {
        case Some(argss) if argss.length > 1 || (!argss.isEmpty && !argss(0).isEmpty) =>
          argss.map(_.map(Param));
        case _ =>
          var i = 0
          val ret = for (tpe <- sym.tpe.paramTypes) yield {
            val ret = sym.newValueParameter(sym.pos, newTermName("arg" + i));
            ret.setInfo(tpe);
            i += 1
            Param(ret)
          }
          if (ret.isEmpty) Nil
          else ret :: Nil
      }
      override def kind = "def"
    }
    case class Val(override val sym: TermSymbol) extends ValDef(sym) {
      def resultType0: Type = sym.tpe
      override def kind = {
        import symtab.Flags._
        if (sym.hasFlag(ACCESSOR)) {
          val setterName = nme.getterToSetter(sym.name);
          val setter = sym.owner.info.decl(setterName);
          if (setter == NoSymbol) "val" else "var";
        } else {
          assert(sym.hasFlag(JAVA));
          if (sym.hasFlag(FINAL)) "val" else "var";
        }
      }
    }
    case class AbstractType(override val sym: Symbol) extends Member(sym) {
      override def kind = "type"
    }

    abstract class NestedClassOrObject(override val sym: Symbol) extends Member(sym) with ClassOrObject {
      override def path: List[ClassOrObject] = ClassOrObject.this.path ::: (super.path);
    }

    case class NestedClass(override val sym : ClassSymbol) extends NestedClassOrObject(sym) with Clazz;
    case class NestedObject(override val sym : ModuleSymbol) extends NestedClassOrObject(sym) with Object;
    def isVisible(sym : Symbol): Boolean = {
      import symtab.Flags._
      if (sym.isLocalClass) return false
      if (sym.isLocal) return false
      if (sym.isPrivateLocal) return false
      if (sym.hasFlag(PRIVATE)) return !inIDE //false
      if (sym.hasFlag(SYNTHETIC)) return false
      if (sym.hasFlag(BRIDGE)) return false
      if (sym.nameString.indexOf("$") != -1) return false
      if (sym.hasFlag(CASE) && sym.isMethod) return false
      return true
    }
    def Member(sym: Symbol): Option[Member] = {
      import global._
      import symtab.Flags
      if (!isVisible(sym)) return None;
      if (!isAccessible(sym)) return None;
      if (sym.hasFlag(Flags.ACCESSOR)) {
        if (sym.isSetter) return None;
        assert(sym.isGetter);
        return Some[Member](new Val(sym.asInstanceOf[TermSymbol]));
      } else if (sym.isValue && !sym.isMethod && !sym.isModule) {
        if (!sym.hasFlag(Flags.JAVA)) {
          Console.println("SYM: " + sym + " " + sym.fullNameString('.'));
          Console.println("FLA: " + Flags.flagsToString(sym.flags));
        }
        assert(sym.hasFlag(Flags.JAVA))
        return Some[Member](new Val(sym.asInstanceOf[TermSymbol]))
      }
      if (sym.isValue && !sym.isModule) {
        val str = Flags.flagsToString(sym.flags);
        assert(sym.isMethod);
        return new Some[Member](new Def(sym.asInstanceOf[TermSymbol]));
      }
      if (sym.isAliasType || sym.isAbstractType) return Some(new AbstractType(sym));
      if (sym.isClass) return Some(new NestedClass(sym.asInstanceOf[ClassSymbol]));
      if (sym.isModule) return Some(new NestedObject(sym.asInstanceOf[ModuleSymbol]));
      None;
    }

  }
  case class Category(label : String)(g : Symbol => Boolean) {
    val f = g
    def plural = label + "s"
  }
  val Constructors = new Category("Additional Constructor")(e => e.isConstructor && !e.isPrimaryConstructor) {
    // override def plural = "Additional Constructors";
  }
  val Objects = Category("Object")(_.isModule);
  val Classes = new Category("Class")(_.isClass) {
    override def plural = "Classes";
  }

  def isValOrVar(e: Symbol) = ((e.isValue) && e.hasFlag(symtab.Flags.ACCESSOR))
  def isChildObject(e: Symbol) = (e.isModule && !e.isRoot)
  /** a method with no parameters and return type != Unit */
  def isDefField(e: Symbol) = (e.isMethod && !e.isConstructor && !e.hasFlag(symtab.Flags.ACCESSOR) && (e.tpe.kind != "NoType"))

  //isValOrVar(e) || isChildObject(e) || isDefField(e)
  //  ((e.isValue) && e.hasFlag(symtab.Flags.ACCESSOR)) || (e.isModule && !e.isRoot) || (e.isMethod && !e.isConstructor && !e.hasFlag(symtab.Flags.ACCESSOR) && (e.tpe.kind != "NoType"))
//  val ExFields = new Category("ExField")(e => (e.isValue) && e.hasFlag(symtab.Flags.ACCESSOR)) {
//    override def plural = "Extended Fields";
//  }

  //val Values = new Category("Value")(e => (e.isValue) && e.hasFlag(symtab.Flags.ACCESSOR)) {
  // && (e.tpe.isInstanceOf[scala.tools.nsc.symtab.Types.NoType])
  val Values = new Category("Value")(e => ((e.isValue) && e.hasFlag(symtab.Flags.ACCESSOR)) ||
    (e.isModule && !e.isRoot) ||
    (e.isMethod && !e.isConstructor && !e.hasFlag(symtab.Flags.ACCESSOR) && (e.typeParams.length < 1) )) {
    override def plural = "Values and Variables";
  }
  val Methods = Category("Method")(e => e.isValue && e.isMethod && !e.isConstructor && !e.hasFlag(symtab.Flags.ACCESSOR));
  val Types = Category("Type")(e => e.isAliasType || e.isAbstractType);

  val categories = Constructors :: Values :: Types :: Methods :: Classes :: Objects :: Nil;


  import java.util.regex.Pattern
  // patterns for standard tags with 1 and 2 arguments
  private val pat1 = Pattern.compile(
    "[ \t]*@(author|deprecated|pre|return|see|since|todo|version|ex|note|codeAsDoc)[ \t]*(.*)")
  private val pat2 = Pattern.compile(
    "[ \t]*@(exception|param|throws)[ \t]+(\\p{Graph}*)[ \t]*(.*)")

  def sort[E <: Entity](entities : Iterable[E]) : Iterable[E] = {
    val set = new jcl.TreeSet[E]()({eA : E => new Ordered[E] {
      def compare(eB : E) : Int = {
        if (eA eq eB) return 0;
        (eA,eB) match {
        case (eA:ClassOrObject,eB:ClassOrObject) =>
          val diff = ModelExtractor.this.compare(eA.path, eB.path);
          if (diff!= 0) return diff;
        case _ =>
        }
        if (eA.getClass != eB.getClass) {
          val diff = eA.getClass.getName.compare(eB.getClass.getName);
          assert(diff != 0);
          return diff;
        }
        if (!eA.sym0.isPackage) {
          val diff = eA.sym0.nameString compare eB.sym0.nameString;
          if (diff != 0) return diff;
        }
        val diff0 = eA.sym0.fullNameString compare eB.sym0.fullNameString;
        assert(diff0 != 0);
        return diff0;
      }
    }});
    set addAll entities;
    set
  }
}
